﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using ICSharpCode.NRefactory.PatternMatching;
using iLynx.Common;
using iLynx.Common.Threading;
using iLynx.Common.WPF;
using iLynx.Common.WPF.Imaging;
using iLynx.GameOfLife.Logic;

namespace iLynx.GameOfLife.ViewModels
{
    public class MainWindowViewModel : NotificationBase
    {
        private readonly IBitmapRenderer firstRenderer;
        private readonly IBitmapRenderer secondRenderer;
        private readonly LifeRenderer firstLogic;
        private readonly LifeRenderer secondLogic;
        private readonly IDispatcher dispatcher;
        private readonly Timer timer;
        private readonly ObservableCollection<GameOfLifePattern> availablePatterns;

        private FrameworkElement imageControl;
        private ICommand imageLoadedCommand;
        private ICommand clearCommand;
        private bool isMouseDown;
        private MouseButton button;
        private BitmapSource firstImage;
        private int height;
        private int width;
        private double scale = 4d;
        private bool isPaused;
        private double speed = 10d;
        private BitmapSource secondImage;

        public MainWindowViewModel(LifeRenderer firstLogic,
                                   LifeRenderer secondLogic)
        {
            var threadManager = new ThreadManager(RuntimeCommon.DefaultLogger);
            dispatcher = new WPFApplicationDispatcher();
            firstRenderer = new UnmanagedBitmapRenderer(threadManager, dispatcher);
            secondRenderer = new UnmanagedBitmapRenderer(threadManager, dispatcher);
            this.firstLogic = firstLogic;
            this.secondLogic = secondLogic;
            firstLogic.SetInterval(speed);
            timer = new Timer(DoResize);
            StartRenderer(firstRenderer, firstLogic, FirstRendererOnSourceCreated);
            StartRenderer(secondRenderer, secondLogic, SecondRendererOnSourceCreated);
            availablePatterns = new ObservableCollection<GameOfLifePattern>
                                {
                                    new GameOfLifePattern
                                    {
                                        Name = "Single Cell",
                                        Pattern = new byte[,]
                                                  {
                                                      { 1 }
                                                  }
                                    },
                                    new GameOfLifePattern
                                    {
                                        Pattern = new byte[,]
                                                  {
                                                      {1, 1, 1},
                                                      {0, 0, 1},
                                                      {0, 1, 0}
                                                  }.SwapPlanes(),
                                        Name = "NE Glider"
                                    },
                                    new GameOfLifePattern
                                    {
                                        Pattern = new byte[,]
                                                  {
                                                      {1, 1, 1},
                                                      {1, 0, 0},
                                                      {0, 1, 0}
                                                  }.SwapPlanes(),
                                        Name = "NW Glider"
                                    },
                                    new GameOfLifePattern
                                    {
                                        Pattern = new byte[,]
                                                  {
                                                      {1, 0, 0},
                                                      {1, 0, 1},
                                                      {1, 1, 0}
                                                  }.SwapPlanes(),
                                        Name = "SW Glider"
                                    },
                                    new GameOfLifePattern
                                    {
                                        Pattern = new byte[,]
                                                  {
                                                      {0, 0, 1},
                                                      {1, 0, 1},
                                                      {0, 1, 1}
                                                  }.SwapPlanes(),
                                        Name = "SE Glider"
                                    },
                                    new GameOfLifePattern
                                    {
                                        Pattern = new byte[,]
                                                  {
                                                      { 0,0,1,1,1,0,0,0,1,1,1,0,0},
                                                      { 0,0,0,0,0,0,0,0,0,0,0,0,0},
                                                      { 1,0,0,0,0,1,0,1,0,0,0,0,1},
                                                      { 1,0,0,0,0,1,0,1,0,0,0,0,1},
                                                      { 1,0,0,0,0,1,0,1,0,0,0,0,1},
                                                      { 0,0,1,1,1,0,0,0,1,1,1,0,0},
                                                      { 0,0,0,0,0,0,0,0,0,0,0,0,0},
                                                      { 0,0,1,1,1,0,0,0,1,1,1,0,0},
                                                      { 1,0,0,0,0,1,0,1,0,0,0,0,1},
                                                      { 1,0,0,0,0,1,0,1,0,0,0,0,1},
                                                      { 1,0,0,0,0,1,0,1,0,0,0,0,1},
                                                      { 0,0,0,0,0,0,0,0,0,0,0,0,0},
                                                      { 0,0,1,1,1,0,0,0,1,1,1,0,0}
                                                  }.SwapPlanes(),
                                        Name = "Pulsar"
                                    },
                                    new GameOfLifePattern
                                    {
                                        Pattern = new byte[,]
                                                  {
                                                      {0,0,0,0,1,0,0,0,0,0,1,0,0,0,0},
                                                      {0,0,0,1,1,1,0,0,0,1,1,1,0,0,0},
                                                      {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                                                      {0,1,0,0,0,0,1,0,1,0,0,0,0,1,0},
                                                      {1,1,0,0,0,0,1,0,1,0,0,0,0,1,1},
                                                      {0,1,0,0,0,0,1,0,1,0,0,0,0,1,0},
                                                      {0,0,0,1,1,1,0,0,0,1,1,1,0,0,0},
                                                      {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                                                      {0,0,0,1,1,1,0,0,0,1,1,1,0,0,0},
                                                      {0,1,0,0,0,0,1,0,1,0,0,0,0,1,0},
                                                      {1,1,0,0,0,0,1,0,1,0,0,0,0,1,1},
                                                      {0,1,0,0,0,0,1,0,1,0,0,0,0,1,0},
                                                      {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                                                      {0,0,0,1,1,1,0,0,0,1,1,1,0,0,0},
                                                      {0,0,0,0,1,0,0,0,0,0,1,0,0,0,0},
                                                  }.SwapPlanes(),
                                        Name = "Exploding Pulsar"
                                    },
                                    new GameOfLifePattern
                                    {
                                        Pattern = new byte[,]
                                                  {
                                                      { 1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,0,1,1,1,1,1}
                                                  }.SwapPlanes(),
                                        Name = "Linear"
                                    }
                                };
            SelectedPattern = availablePatterns.FirstOrDefault();
        }

        public ObservableCollection<GameOfLifePattern> Patterns
        {
            get { return availablePatterns; }
        }

        private GameOfLifePattern selectedPattern;
        public GameOfLifePattern SelectedPattern
        {
            get { return selectedPattern; }
            set
            {
                if (value == selectedPattern) return;
                selectedPattern = value;
                OnPropertyChanged();
            }
        }

        private void StartRenderer(IBitmapRenderer renderer, LifeRenderer logic, Action<BitmapSource> sourceCreatedCallback)
        {
            renderer.RegisterRenderCallback(logic.Render, 0);
            renderer.SourceCreated += sourceCreatedCallback;
            renderer.ClearEachPass = true;
            renderer.Start();
        }

        public ICommand ClearCommand
        {
            get { return clearCommand ?? (clearCommand = new DelegateCommand(OnClear)); }
        }

        private void OnClear()
        {
            firstLogic.Clear();
            secondLogic.Clear();
        }

        private int RenderWidth
        {
            get { return (int)(width / scale); }
        }

        private int RenderHeight
        {
            get { return (int)(height / scale); }
        }

        private void DoResize(object state)
        {
            firstRenderer.ChangeRenderSize(RenderWidth, RenderHeight);
            secondRenderer.ChangeRenderSize(RenderWidth, RenderHeight);
            firstLogic.IsRunning = true;
            secondLogic.IsRunning = true;
        }

        private void FirstRendererOnSourceCreated(BitmapSource bitmapSource)
        {
            if (!dispatcher.CheckAccess())
                dispatcher.Invoke(() => FirstRendererOnSourceCreated(bitmapSource));
            else
            {
                firstImage = bitmapSource;
                RaisePropertyChanged(() => FirstImage);
            }
        }

        private void SecondRendererOnSourceCreated(BitmapSource bitmapSource)
        {
            if (!dispatcher.CheckAccess())
                dispatcher.Invoke(() => SecondRendererOnSourceCreated(bitmapSource));
            else
            {
                secondImage = bitmapSource;
                RaisePropertyChanged(() => SecondImage);
            }
        }

        public ICommand ImageLoadedCommand
        {
            get { return imageLoadedCommand ?? (imageLoadedCommand = new DelegateCommand<FrameworkElement>(OnImageLoaded)); }
        }

        private void OnImageLoaded(FrameworkElement frameworkElement)
        {
            imageControl = frameworkElement;
            imageControl.PreviewMouseDown += ImageControlOnPreviewMouseDown;
            imageControl.PreviewMouseUp += ImageControlOnPreviewMouseUp;
            imageControl.PreviewMouseMove += ImageControlOnPreviewMouseMove;
        }

        private void ImageControlOnPreviewMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            if (!isMouseDown) return;
            if (null == firstLogic) return;
            if (null == imageControl) return;
            KillOrSeed(mouseEventArgs);
        }

        private void ImageControlOnPreviewMouseUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            isMouseDown = false;
            if (isPaused) return;
            firstLogic.Resume();
            secondLogic.Resume();
        }

        private void ImageControlOnPreviewMouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            isMouseDown = true;
            firstLogic.Pause();
            secondLogic.Pause();
            button = mouseButtonEventArgs.ChangedButton;
            KillOrSeed(mouseButtonEventArgs);
        }

        private void KillOrSeed(MouseEventArgs e)
        {
            switch (button)
            {
                case MouseButton.Left:
                    SeedLife(e);
                    break;
                case MouseButton.Right:
                    Kill(e);
                    break;
            }
        }

        public bool IsPaused
        {
            get { return isPaused; }
            set
            {
                if (value == isPaused) return;
                isPaused = value;
                if (isPaused)
                {
                    firstLogic.Pause();
                    secondLogic.Pause();
                }
                else
                {
                    firstLogic.Resume();
                    secondLogic.Resume();
                }
                OnPropertyChanged();
            }
        }

        public double Speed
        {
            get { return speed; }
            set
            {
                if (Math.Abs(value - speed) < double.Epsilon) return;
                speed = value;
                firstLogic.SetInterval(speed);
                secondLogic.SetInterval(speed);
                OnPropertyChanged();
            }
        }

        private void Kill(MouseEventArgs e)
        {
            var point = e.GetPosition(imageControl);
            firstLogic.KillLife((int)(point.X / scale), (int)(point.Y / scale));
            secondLogic.KillLife((int)(point.X / scale), (int)(point.Y / scale));
        }

        private void SeedLife(MouseEventArgs e)
        {
            var point = e.GetPosition(imageControl);
            var pattern = selectedPattern.Pattern;
            var x = (int) (point.X/scale) - pattern.GetLength(0)/2;
            var y = (int) (point.Y/scale) - pattern.GetLength(1)/2;
            firstLogic.InsertPattern(x, y, pattern);
            secondLogic.InsertPattern(x, y, pattern);
        }

        #region Overrides of NotificationBase

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public override void Dispose()
        {
            base.Dispose();
            firstRenderer.Stop();
            firstLogic.IsRunning = false;
            firstLogic.Dispose();
        }

        #endregion

        public int Width
        {
            get { return width; }
            set
            {
                if (value == width) return;
                width = value;
                firstLogic.Width = RenderWidth;
                secondLogic.Width = RenderWidth;
                Reset();
                OnPropertyChanged();
            }
        }

        public int Height
        {
            get { return height; }
            set
            {
                if (value == height) return;
                height = value;
                firstLogic.Height = RenderHeight;
                secondLogic.Height = RenderHeight;
                Reset();
                OnPropertyChanged();
            }
        }

        public double Scale
        {
            get { return scale; }
            set
            {
                if (Math.Abs(value - scale) < double.Epsilon) return;
                scale = value;
                firstLogic.Width = RenderWidth;
                firstLogic.Height = RenderHeight;
                secondLogic.Width = RenderWidth;
                secondLogic.Height = RenderHeight;
                Reset();
                OnPropertyChanged();
            }
        }

        private void Reset()
        {
            if (0 >= width || 0 >= height) return;
            timer.Change(125, 0);
        }

        public BitmapSource FirstImage
        {
            get { return firstImage; }
        }

        public BitmapSource SecondImage
        {
            get { return secondImage; }
        }
    }

    public class GameOfLifePattern
    {
        public byte[,] Pattern { get; set; }
        public string Name { get; set; }
        private ICommand clickCommand;
        public ICommand CLickCommand { get { return clickCommand ?? (clickCommand = new DelegateCommand(OnSelected)); } }
        public event EventHandler<byte[,]> Selected;

        protected void OnSelected()
        {
            var handler = Selected;
            if (null == handler) return;
            handler(this, Pattern);
        }
    }
}
